
import os
import re
import wx
import wx.lib.newevent
import threading
import Queue

import Env
import Misc

from NoteBook import NoteBook

import IconImages

from ConsolePanel import ConsolePanel
from VariablesPanel import VariablesPanel

import Config
Config.LoadConfig()

import LuaDebuggerLib

Client = LuaDebuggerLib.JSONLuaDebuggerClient

# {{{ debugger thread
(NewObjectEvent, EVT_NEW_OBJECT) = wx.lib.newevent.NewEvent()

import threading
class ClientThread(threading.Thread):
	def __init__(self, win, debugger, name='ClientThread'):
		assert debugger

		self.win = win
		self.debugger = debugger
		self.queue = Queue.Queue()

		threading.Thread.__init__(self, name=name)

	def run(self):
		obj = self.debugger.Connect()
		self.post(obj)
		while True:
			op = self.queue.get()
			if op == -1:
				break
			obj = self.debugger.Op(op)
			self.post(obj)
			if obj == None:
				break

	def op(self, op):
		self.queue.put(op)
	
	def post(self, obj):
		event = NewObjectEvent(object = obj)
		wx.PostEvent(self.win, event)
	
	def join(self, timeout=None):
		self.queue.put(-1)
		threading.Thread.join(self, timeout)

# }}}

# {{{ MainFrame
class MainFrame(wx.Frame):

	def __init__(self):
		wx.Frame.__init__(self, None, -1, "Toy Lua", size=(800, 600))

		icon  = wx.EmptyIcon()
		icon.CopyFromBitmap(IconImages.getToyLuaBitmap())
		self.SetIcon(icon)

		self.InitIcons()

		#splitter = wx.SplitterWindow(self, style=wx.BORDER_SUNKEN)
		#splitter = wx.SplitterWindow(self, style=wx.BORDER_SIMPLE)
		splitter = wx.SplitterWindow(self, style=wx.BORDER_NONE)

		self.nb = NoteBook(splitter, -1)
		self.nb.RegisterGetTabTitle(self.GetRelativePath)
		self.nb.RegisterCloseTab(self.AfterCloseAllTabs)

		splitter2 = wx.SplitterWindow(splitter)

		self.console = ConsolePanel(splitter2, -1)
		self.console.RegisterTerminate(self.ModeNormal)
		self.variables = VariablesPanel(splitter2)

		splitter2.SetMinimumPaneSize(10)
		splitter2.SplitVertically(self.console, self.variables, -320)

		splitter.SetMinimumPaneSize(20)
		splitter.SplitHorizontally(self.nb, splitter2, -100)

		self.splitter = splitter
		self.splitter2 = splitter2

		statusBar = self.CreateStatusBar()

		self.createMenuBar()
		self.createToolBar()

		#self.Bind(wx.EVT_SIZE, self.OnSize)
		self.Bind(wx.EVT_CLOSE, self.OnClose)
		self.Bind(EVT_NEW_OBJECT, self.OnReceive)

		self.workdir = os.path.normpath(os.path.abspath(os.getcwd()))
		self.srcdir = self.workdir
		self.debugger = None
		self.debuggerThread = None

		#Initilize library path
		Env.initenv()

		self.ModeNone()
		self.OpenTab( r'../test/Test.lua' )



	def InitIcons(self):
		self.newIcon = wx.ArtProvider_GetBitmap(wx.ART_NEW, wx.ART_OTHER, (16, 16))
		self.openIcon = wx.ArtProvider_GetBitmap(wx.ART_FILE_OPEN, wx.ART_OTHER, (16, 16))

		self.runIcon = IconImages.getRunBitmap()
		self.runIconOff = IconImages.getRunOffBitmap()

		self.debugIcon = IconImages.getDebugBitmap()
		self.debugIconOff = IconImages.getDebugOffBitmap()

		self.resumeIcon = IconImages.getDebugResumeBitmap()
		self.resumeIconOff = IconImages.getDebugResumeOffBitmap()
		self.stepIntoIcon = IconImages.getDebugStepIntoBitmap()
		self.stepIntoIconOff = IconImages.getDebugStepIntoOffBitmap()
		self.stepOverIcon = IconImages.getDebugStepOverBitmap()
		self.stepOverIconOff = IconImages.getDebugStepOverOffBitmap()
		self.stepReturnIcon = IconImages.getDebugStepReturnBitmap()
		self.stepReturnIconOff = IconImages.getDebugStepReturnOffBitmap()
		self.terminateIcon = IconImages.getDebugTerminateBitmap()
		self.terminateIconOff = IconImages.getDebugTerminateOffBitmap()


	# {{{ MenuBar
	# {{{ Menu Data
	def menuData(self):
		return [
				("&File", (
					("&New\tCtrl+T", "New file", self.OnNew),
					("&Open", "Open file", self.OnOpen),
					("Open &Tab", "Open file in a new tab", self.OnOpenTab),
					("&Save\tCtrl+S", "Save file", self.OnSave),
					("&Close", "Cloase file", self.OnCloseTab),
					("", "", ""),
					("E&xit", "Exit", self.OnExit),
					("", "", ""),
					)),
				#("&Edit", (
				#	("Copy", "", self.OnAll),
				#	("Cut", "", self.OnAll),
				#	("Paste", "", self.OnAll),
				#	("", "", ""),
				#	("&Options", "", self.OnAll),
				#	)),
				("&Lua", 
					self.getNormalMenuData()
					),
				("&Help", (
					("&About", "", self.OnAll),
					)),
			]
	def genDebugMenuData(self, values):
		data = [
					["Run", "Ctrl+F5", self.OnRun, False, self.runIcon, self.runIconOff],
					["Debug", "F5", self.OnDebug, False, self.debugIcon, self.debugIconOff],
					["", "", ""],
					["Resume", "F8", self.OnResume, False, self.resumeIcon, self.resumeIconOff],
					["Step Into", "F5", self.OnStepInto, False, self.stepIntoIcon, self.stepIntoIconOff],
					["Step Over", "F6", self.OnStepOver, False, self.stepOverIcon, self.stepOverIconOff],
					["Step Return", "F7", self.OnStepReturn, False, self.stepReturnIcon, self.stepReturnIconOff],
					["Terminate", "F9", self.OnTerminate, False, self.terminateIcon, self.terminateIconOff],
					["", "", ""],
					["Add/Remove Breakpoint", "F4", self.OnAddRemoveBP, False],
					["Remove All Breakpoints", "", self.OnRemoveAllBPs, False]
				]
		assert not values[2] or not values[5]
		assert len(data) == len(values)
		for i in range(len(data)):
			if values[i] == "" or data[i][0] == "":
				assert values[i] =="" and data[i][0] == ""
				continue
			if values[i]:
				if data[i][1] != "":
					data[i][0] = data[i][0] + '\t' + data[i][1]
				data[i][1] = ""
				data[i][3] = True
			else:
				data[i][1] = ""
		return data


	def getNoneMenuData(self):
		v = ( 0, 0, "", 0, 0, 0, 0, 0, "", 0, 0,)
		return self.genDebugMenuData( v )

	def getRunMenuData(self):
		v = ( 0, 0, "", 0, 0, 0, 0, 0, "", 1, 1,)
		return self.genDebugMenuData( v )

	def getDebugMenuData(self):
		v = ( 0, 0, "", 1, 1, 1, 1, 1, "", 1, 1,)
		return self.genDebugMenuData( v )

	def getNormalMenuData(self):
		v = ( 1, 1, "", 0, 0, 0, 0, 0, "", 1, 1,)
		return self.genDebugMenuData( v )

	def getWaitMenuData(self):
		v = ( 0, 0, "", 0, 0, 0, 0, 1, "", 1, 1,)
		return self.genDebugMenuData( v )

	# }}}


	def createMenuBarFrom(self, data):
		menuBar = wx.MenuBar()
		for eachMenuData in data:
			menuLabel = eachMenuData[0]
			menuItems = eachMenuData[1]
			menuBar.Append(self.createMenu(menuItems), menuLabel)
		self.SetMenuBar(menuBar)

	def createMenuBar(self):
		self.createMenuBarFrom(self.menuData())

	def createMenu(self, menuData):
		menu = wx.Menu()
		for eachItem in menuData:
			if len(eachItem) == 2:
				label = eachItem[0]
				subMenu = self.createMenu(eachItem[1])
				menu.AppendMenu(wx.NewId(), label, subMenu)
			else:
				self.createMenuItem(menu, *eachItem)
		return menu

	def createMenuItem(self, menu, label, status, handler, enabled = None, icon = None, icon2 = None):
		kind = wx.ITEM_NORMAL

		if not label:
			menu.AppendSeparator()
			return
		menuItem = wx.MenuItem(menu, wx.NewId(), label, status, kind)
		if icon:
			menuItem.SetBitmaps(icon, icon2)
		menu.AppendItem(menuItem)
		if enabled == False:
			menuItem.Enable(enabled)
		#item.SetBitmap(images.getSmilesBitmap())
		#menuItem = menu.Append(-1, label, status, kind)
		#if enabled == False:
		#	menuItem.Enable(enabled)
		#if icon:
		#	menuItem.SetBitmaps(icon, icon2)
		self.Bind(wx.EVT_MENU, handler, menuItem)
	
	def useNoneMenuBar(self):
		luaMenu = self.createMenu(self.getNoneMenuData())
		menubar = self.GetMenuBar()
		menubar.Replace(1, luaMenu, "Lua")

	def useRunMenuBar(self):
		luaMenu = self.createMenu(self.getRunMenuData())
		menubar = self.GetMenuBar()
		menubar.Replace(1, luaMenu, "Lua")

	def useDebugMenuBar(self):
		luaMenu = self.createMenu(self.getDebugMenuData())
		menubar = self.GetMenuBar()
		menubar.Replace(1, luaMenu, "Lua")

	def useNormalMenuBar(self):
		luaMenu = self.createMenu(self.getNormalMenuData())
		menubar = self.GetMenuBar()
		menubar.Replace(1, luaMenu, "Lua")

	def useWaitMenuBar(self):
		luaMenu = self.createMenu(self.getWaitMenuData())
		menubar = self.GetMenuBar()
		menubar.Replace(1, luaMenu, "Lua")
	# }}}

	# {{{ ToolBar
	def createTool(self, toolbar, label, icon1, icon2):
		return toolbar.AddLabelTool(wx.NewId(), label, icon1, icon2, shortHelp=label)

	def createToolBar(self):
		toolbar = self.CreateToolBar()

		new = toolbar.AddSimpleTool(wx.NewId(), self.newIcon, "New")
		self.Bind(wx.EVT_MENU, self.OnNew, new)

		open = toolbar.AddSimpleTool(wx.NewId(), self.openIcon, "Open")
		self.Bind(wx.EVT_MENU, self.OnOpen, open)

		toolbar.AddSeparator()

		run = self.createTool(toolbar, "Run", self.runIcon, self.runIconOff)
		self.Bind(wx.EVT_MENU, self.OnRun, run)

		debug = self.createTool(toolbar, "Debug", self.debugIcon, self.debugIconOff)
		self.Bind(wx.EVT_MENU, self.OnDebug, debug)

		toolbar.AddSeparator()

		resume = self.createTool(toolbar, "Resume", self.resumeIcon, self.resumeIconOff)
		self.Bind(wx.EVT_MENU, self.OnResume, resume)
		resume.Enable(False)

		stepinto = self.createTool(toolbar, "Step Into", self.stepIntoIcon, self.stepIntoIconOff)
		self.Bind(wx.EVT_MENU, self.OnStepInto, stepinto)
		stepinto.Enable(False)

		stepover = self.createTool(toolbar, "Step Over", self.stepOverIcon, self.stepOverIconOff)
		self.Bind(wx.EVT_MENU, self.OnStepOver, stepover)
		stepover.Enable(False)

		stepreturn = self.createTool(toolbar, "Step Return", self.stepReturnIcon, self.stepReturnIconOff)
		self.Bind(wx.EVT_MENU, self.OnStepReturn, stepreturn)
		stepreturn.Enable(False)

		terminate = self.createTool(toolbar, "Terminate", self.terminateIcon, self.terminateIconOff)
		self.Bind(wx.EVT_MENU, self.OnTerminate, terminate)
		terminate.Enable(False)


		self.tools = [
				run,
				debug,
				resume,
				stepinto,
				stepover,
				stepreturn,
				terminate,
				]
		self.toolbar = toolbar
		self.toolbarValues = [ 1, 1, 0, 0, 0, 0, 0, ]

		toolbar.Realize()
	
	def useToolBar(self, values):
		if self.toolbarValues == values:
			return
		self.toolbarValues = values

		# FIXME: flash
		for i in range(len(values)):
			self.toolbar.EnableTool(self.tools[i].GetId(), values[i])

	def useNoneToolBar(self):
		values = [ 0, 0, 0, 0, 0, 0, 0, ]
		self.useToolBar(values)

	def useRunToolBar(self):
		values = [ 0, 0, 0, 0, 0, 0, 1, ]
		self.useToolBar(values)

	def useDebugToolBar(self):
		values = [ 0, 0, 1, 1, 1, 1, 1, ]
		self.useToolBar(values)

	def useNormalToolBar(self):
		values = [ 1, 1, 0, 0, 0, 0, 0, ]
		self.useToolBar(values)

	def useWaitToolBar(self):
		values = [ 0, 0, 0, 0, 0, 0, 1, ]
		self.useToolBar(values)

	# }}}

	def OnAll(self, event):
		print(event)

	def OnSize(self, event):
		size = self.GetSize()
		self.splitter.SetSize(size)
		w, h = size
		h = -h/3
		self.splitter.SetSashPosition(h)
		w = -w*2/5
		self.splitter2.SetSashPosition(w)

	# {{{ File event handlers
	def GetFileFromDialog(self):
		filepath = None
		dlg = wx.FileDialog(self, "Open Lua file...", "",
				style=wx.OPEN, wildcard='*.lua')
		if dlg.ShowModal() == wx.ID_OK:
			filepath = dlg.GetPath()
		dlg.Destroy()
		return filepath

	def OnNew(self, event):
		self.nb.NewTab()

	def OnOpen(self, event):
		filepath = self.GetFileFromDialog()
		if filepath:
			self.Open(filepath)

	def OnOpenTab(self, event):
		filepath = self.GetFileFromDialog()
		if filepath:
			self.OpenTab(filepath)
	
	def OnCloseTab(self, event):
		self.nb.CloseCurrentTab()

	def OnSave(self, event):
		if self.nb.GetPageCount() > 0:
			self.CurrentTab().Save()
			if self.mode == 'none':
				self.ModeNormal()

	def OnExit(self, event):
		# TODO: check modified file
		self.Close()
	
	def OnClose(self, event):
		if self.debuggerThread:
			self.debuggerThread.join()
		self.Destroy()


	# }}}
	
	# {{{ Debug event handlers
	def OnRun(self, event):
		if self.CurrentTab().filepath:
			self.ModeRun()

	def OnDebug(self, event):
		if self.CurrentTab().filepath:
			self.ModeDebug()
			self.ModeWait()

	def OnResume(self, event):
		self.OnOp('rm')

	def OnStepInto(self, event):
		self.OnOp('si')

	def OnStepOver(self, event):
		self.OnOp('so')

	def OnStepReturn(self, event):
		self.OnOp('sr')
	
	def OnTerminate(self, event):
		if self.debugger:
			self.OnOp('end')
			self.debuggerThread.op('end')

	def OnOp(self, op):
		assert( type(op) == str )
		if not self.debugger:
			return
		op = { 'Op':op, 'Mode':'o', 'BreakPoints':self.GetAllBreakPoints() }
		self.debuggerThread.op(op)
		op = { 'Op':'g', 'Mode':'r' }
		self.debuggerThread.op(op)
		op = { 'Op':'u', 'Mode':'r' }
		self.debuggerThread.op(op)
		op = { 'Op':'l', 'Mode':'r' }
		self.debuggerThread.op(op)
		self.ModeWait()
	
	def OnReceive(self, event):
		obj = event.object
		if obj:
			self.DisplayResults(obj)
			self.CurrentTab().Update()
			self.ModeHit()
		else:
			self.ModeNormal()

	def OnAddRemoveBP(self, event):
		self.CurrentTab().AddRemoveBP()

	def OnAddBP(self, event):
		self.CurrentTab().AddBP()

	def OnRemoveBP(self, event):
		self.CurrentTab().RemoveBP()

	def OnRemoveAllBPs(self, event):
		self.CurrentTab().ClearBPs()
	
	# }}}

	def AfterCloseAllTabs(self):
		if self.mode == 'normal' and self.nb.GetPageCount() == 0:
			self.ModeNone()

	def OpenTab(self, filepath):
		assert self.workdir
		tab = self.nb.OpenTab(filepath)

		if self.mode == 'none':
			self.ModeNormal()
		return tab

	def CloseTab(self, n):
		self.nb.CloseTab(n)

	def Open(self, filepath):
		tab = self.nb.Open(filepath)
		if self.mode == 'none':
			self.ModeNormal()
		return tab

	def CurrentFile(self):
		return self.CurrentTab().filepath

	def CurrentTab(self):
		return self.nb.GetPage(self.nb.GetSelection())

	def DebugOnLine(self, file, line):
		tab = self.Open(file)
		tab.DebugOnLine(line - 1)

	# {{{ Mode
	def ModeNone(self):
		self.mode = 'none'
		self.useNoneMenuBar()
		self.useNoneToolBar()

	def ModeDebug(self):
		self.mode = 'debug'
		self.useDebugMenuBar()
		self.useDebugToolBar()

		filepath = self.CurrentFile()
		cmd = Env.cmddebug(filepath)
		self.ChangeDir(os.path.dirname(filepath))
		self.console.Run(cmd)

		self.debugger = Client()
		self.debuggerThread = ClientThread(self, self.debugger)
		self.debuggerThread.start()

		self.debugger.globals = []
		self.debugger.locals = []
		self.debugger.upvalues = []

	def ModeNormal(self):
		self.mode = 'normal'
		self.debugger = None
		if self.debuggerThread:
			self.debuggerThread.join()
		self.debuggerThread = None

		self.ChangeDir(self.workdir)

		n = self.nb.GetPageCount()
		tab = None
		for i in range(n):
			thistab = self.nb.GetPage(i)
			thistab.DebugNone()

		self.useNormalMenuBar()
		self.useNormalToolBar()
	
	def ModeWait(self):
		self.mode = 'wait'
		self.useWaitMenuBar()
		self.useWaitToolBar()

	def ModeHit(self):
		self.mode = 'debug'
		self.useDebugMenuBar()
		self.useDebugToolBar()

	def ModeRun(self):
		self.mode = 'run'
		self.useRunMenuBar()
		self.useRunToolBar()

		filepath = self.CurrentFile()
		cmd = Env.cmdrun(filepath)
		self.ChangeDir(os.path.dirname(filepath))
		self.console.Run(cmd)
	# }}}

	def GetAllBreakPoints(self):
		n = self.nb.GetPageCount()
		bps = []
		for i in range(n):
			tab = self.nb.GetPage(i)
			if tab.filepath == None:
				continue
			File = '@'+tab.relativepath
			for line in tab.breakpoints:
				bps.append( { 'File':File, 'Line':line, 'Type':'Line' } )
		return bps
	
	def DisplayResults(self, obj):
		if type(obj) == list or type(obj) == tuple:
			self.DebugOnLine(*obj)
		elif type(obj) == dict:
			if obj.has_key('Info'):
				self.DebugOnLine(obj['Info']['short_src'], obj['Info']['currentline'])
			elif obj.has_key('Globals'):
				globalVariables = obj['Globals']
				for p in globalVariables:
					if len(p) == 1:
						p.append(None)
				globalVariables.sort()
				self.debugger.globals = globalVariables
				self.DisplayVariables()
			elif obj.has_key('Upvalues'):
				upVariables = obj['Upvalues']
				for p in upVariables:
					if len(p) == 1:
						p.append(None)
				self.debugger.upvalues = upVariables
				self.DisplayVariables()
			elif obj.has_key('Locals'):
				localVariables = obj['Locals']
				for p in localVariables:
					if len(p) == 1:
						p.append(None)
				self.debugger.locals = localVariables
				self.DisplayVariables()
		else:
			assert False, 'list/dict expected, %s %s got' % (type(obj), obj)
	
	def DisplayVariables(self):
		self.variables.ClearVariables()
		self.variables.AppendGlobals(self.debugger.globals)
		self.variables.AppendUpvalues(self.debugger.upvalues)
		self.variables.AppendLocals(self.debugger.locals)

	def GetRelativePath(self, dir):
		return Misc.RelativePath(self.srcdir, dir)

	def ChangeDir(self, dir):
		os.chdir(dir)
		self.srcdir = os.path.normpath( os.path.abspath( dir ) )
		for i in range(self.nb.GetPageCount()):
			tab = self.nb.GetPage(i)
			if tab.filepath:
				tab.relativepath = self.GetRelativePath(tab.filepath)


# }}}


def Main():
	app = wx.PySimpleApp()
	frame = MainFrame()
	frame.Show(True)
	app.MainLoop()

if __name__ == '__main__':
	Main()

# vim: foldmethod=marker:
